import LoopMode from "./LoopMode";
import FrameLabel from "./FrameLabel";
import {clamp} from "../../utils/number/clamp";
import SymbolType from "./SymbolType";

const sMatrix = PIXI.Matrix.TEMP_MATRIX;

export default class Symbol extends PIXI.Sprite {

    constructor(data, atlas) {
        super();
        this._data = data;
        this._atlas = atlas;
        this._composedFrame = -1;
        this._numLayers = data.TIMELINE.LAYERS.length;
        this._numFrames = this.getNumFrames();
        this._frameLabels = this.getFrameLabels();
        this._symbolName = data.SYMBOL_name;
        this._type = SymbolType.GRAPHIC;
        this._loopMode = LoopMode.LOOP;

        this.createLayers();
    }

    get currentLabel() {
        let numLabels = this._frameLabels.length;
        let highestLabel = numLabels ? this._frameLabels[0] : null;
        for (let i = 1; i < numLabels; ++i) {
            var label = this._frameLabels[i];
            if (label.frame <= this._currentFrame) {
                highestLabel = label;
            } else {
                break;
            }
        }
        return highestLabel ? highestLabel.name : null;
    }

    get currentFrame() {
        return this._currentFrame;
    }

    set currentFrame(value) {
        while (value < 0) {
            value += this._numFrames;
        }

        if (this._loopMode == LoopMode.PLAY_ONCE) {
            this._currentFrame = clamp(value, 0, this._numFrames - 1);
        } else {
            this._currentFrame = Math.abs(value % this._numFrames);
        }

        if (this._composedFrame != this._currentFrame) {
            this.update();
        }
    }

    get type() {
        return this._type;
    }

    set type(value) {
        if (SymbolType.isValid(value)) {
            this._type = value;
        } else {
            throw new Error("Invalid symbol type: " + value);
        }
    }

    get loopMode() {
        return this._loopMode;
    }

    set loopMode(value) {
        if (LoopMode.isValid(value)) {
            this._loopMode = value;
        } else {
            throw new Error("Invalid loop mode: " + value);
        }
    }

    get symbolName() {
        return this._symbolName;
    }

    get numLayers() {
        return this._numLayers;
    }

    get numFrames() {
        return this._numFrames;
    }

    reset() {
        sMatrix.identity();
        this.transformationMatrix = sMatrix;
        this.alpha = 1.0;
        this._currentFrame = 0;
        this._composedFrame = -1;
    }

    getFrame(label) {
        let numLabels = this._frameLabels.length;
        for (let i = 0; i < numLabels; ++i) {
            let frameLabel = this._frameLabels[i];
            if (frameLabel.name == label) {
                return frameLabel.frame;
            }
        }
        return -1;
    }

    getLayerData(layerIndex) {
        return this._data.TIMELINE.LAYERS[layerIndex];
    }

    getFrameData(layerIndex, frameIndex) {
        let frames = this.getLayerData(layerIndex).Frames;
        let numFrames = frames.length;
        for (let i = 0; i < numFrames; ++i) {
            let frame = frames[i];
            if (frame.index <= frameIndex &&
                frame.index + frame.duration > frameIndex) {
                return frame;
            }
        }
        return null;
    }

    nextFrame() {
        if (this._loopMode != LoopMode.SINGLE_FRAME) {
            this.currentFrame += 1;
        }
        this.nextFrame_MovieClips();
    }

    nextFrame_MovieClips() {
        if (this._type == SymbolType.MOVIE_CLIP) {
            currentFrame += 1;
        }

        for (let l = 0; l < this._numLayers; ++l) {
            let layer = this.getLayer(l);
            let numElements = layer.numChildren;
            for (let e = 0; e < numElements; ++e) {
                layer.getChildAt(e).nextFrame_MovieClips();
            }
        }
    }

    update() {
        for (let i = 0; i < this._numLayers; ++i) {
            this.updateLayer(i);
        }
        this._composedFrame = this._currentFrame;
    }

    updateLayer(layerIndex) {
        let layer = this.getLayer(layerIndex);
        let frameData = this.getFrameData(layerIndex, this._currentFrame);
        let elements = frameData ? frameData.elements : null;
        let numElements = elements ? elements.length : 0;

        for (let i = 0; i < numElements; ++i) {
            let elementData = elements[i].SYMBOL_Instance;
            let oldSymbol = layer.numChildren > i ? layer.getChildAt(i) : null;
            let newSymbol = null;
            let symbolName = elementData.SYMBOL_name;

            if (!this._atlas.hasSymbol(symbolName)) {
                symbolName = Symbol.BITMAP_SYMBOL_NAME;
            }

            if (oldSymbol && oldSymbol._symbolName == symbolName) {
                newSymbol = oldSymbol;
            } else {
                if (oldSymbol) {
                    if (oldSymbol.parent) {
                        oldSymbol.parent.removeChild(oldSymbol);
                    }
                    this._atlas.putSymbol(oldSymbol);
                }

                newSymbol = this._atlas.getSymbol(symbolName);
                layer.addChildAt(newSymbol, i);
            }

            newSymbol.setTransformationMatrix(elementData.Matrix3D);
            newSymbol.setBitmap(elementData.bitmap);
            newSymbol.setColor(elementData.color);
            newSymbol.setLoop(elementData.loop);
            newSymbol.setType(elementData.symbolType);

            if (newSymbol.type == SymbolType.GRAPHIC) {
                let firstFrame = elementData.firstFrame;
                let frameAge = this._currentFrame - frameData.index;

                if (newSymbol.loopMode == LoopMode.SINGLE_FRAME) {
                    newSymbol.currentFrame = firstFrame;
                } else if (newSymbol.loopMode == LoopMode.LOOP) {
                    newSymbol.currentFrame = (firstFrame + frameAge) % newSymbol._numFrames;
                } else {
                    newSymbol.currentFrame = firstFrame + frameAge;
                }
            }
        }

        let numObsoleteSymbols = layer.numChildren - numElements;

        for (let i = 0; i < numObsoleteSymbols; ++i)
        {
            let oldSymbol = layer.removeChildAt(numElements);
            this._atlas.putSymbol(oldSymbol);
        }
    }

    createLayers() {
        if (this._layers) {
            throw new Error("Method must only be called once");
        }

        this._layers = new PIXI.Sprite();
        this.addChild(this._layers);

        for (var i = 0; i < this._numLayers; ++i) {
            let layer = new PIXI.Sprite();
            layer.name = this.getLayerData(i).Layer_name;
            this._layers.addChild(layer);
        }
    }

    setBitmap(data) {
        if (data) {
            let texture = this._atlas.getTexture(data.name);
            if (this._bitmap) {
                this._bitmap.texture = texture;
            } else {
                this._bitmap = this._atlas.getImage(texture);
                this.addChild(this._bitmap);
            }

            this._bitmap.x = data.Position.x;
            this._bitmap.y = data.Position.y;
        } else if (_bitmap) {
            this._bitmap.x = this._bitmap.y = 0;
            if (this._bitmap.parent) {
                this._bitmap.parent.removeChild(this._bitmap);
            }
            this._atlas.putImage(this._bitmap);
            this._bitmap = null;
        }
    }

    setTransformationMatrix(data) {
        sMatrix.set(data.m00, data.m01, data.m10, data.m11, data.m30, data.m31);
        this.setTransform(sMatrix);
    }

    setColor(data) {
        if (data) {
            this.alpha = data.mode == "Alpha" || data.mode == "Advanced" ? data.alphaMultiplier : 1.0;
        } else {
            this.alpha = 1.0;
        }
    }

    setLoop(data) {
        if (data) {
            this._loopMode = data;
        } else {
            this._loopMode = LoopMode.LOOP;
        }
    }

    setType(data) {
        if (data) {
            _type = data;
        }
    }

    getNumFrames() {
        let numFrames = 0;
        for (let i = 0; i < this._numLayers; ++i) {
            let frameDates = this.getLayerData(i).Frames;
            let numFrameDates = frameDates ? frameDates.length : 0;
            let layerNumFrames = numFrameDates ? frameDates[0].index : 0;

            for (let j = 0; j < numFrameDates; ++j) {
                layerNumFrames += frameDates[j].duration;
            }

            if (layerNumFrames > numFrames) {
                numFrames = layerNumFrames;
            }
        }
        return numFrames || 1;
    }

    getFrameLabels() {
        let labels = [];
        for (let i = 0; i < this._numLayers; ++i) {
            let frameDates = this.getLayerData(i).Frames;
            let numFrameDates = frameDates ? frameDates.length : 0;

            for (let j = 0; j < numFrameDates; ++j) {
                let frameData = frameDates[j];
                if (frameData.hasOwnProperty("name")) {
                    labels[labels.length] = new FrameLabel(frameData.name, frameData.index);
                }
            }
        }
        labels.sort((a, b) => {
            return a.frame - b.frame;
        });
        return labels;
    }

    getLayer(layerIndex) {
        return this._layers.getChildAt(layerIndex);
    }

    getNextLabel(afterLabel = null) {
        let numLabels = this._frameLabels.length;
        let startFrame = this.getFrame(afterLabel || currentLabel);

        for (let i = 0; i < numLabels; ++i) {
            let label = this._frameLabels[i];
            if (label.frame > startFrame) {
                return label.name;
            }
        }

        return this._frameLabels ? this._frameLabels[0].name : null; // wrap around
    }
}

Symbol.BITMAP_SYMBOL_NAME = '___atlas_sprite___';